//
// Created on 2025/6/16.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#ifndef MYAPPLICATION_VARIANT_H
#define MYAPPLICATION_VARIANT_H

#include "napi/native_api.h"
#include "varint/decode.h"
#include "varint/encode.h"
#include <cstdint>
#include <vector>

template <typename T> static napi_value varint_encode_typedarray_impl(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];
    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    size_t length = 0;
    napi_typedarray_type type;
    void *data = nullptr;
    napi_get_typedarray_info(env, argv[0], &type, &length, &data, nullptr, 0);

    const T *buffer = reinterpret_cast<const T *>(data);
    size_t out_size = varint_bytes_total(buffer, length);

    napi_value result;
    uint8_t *out_buf = nullptr;
    napi_create_arraybuffer(env, out_size, reinterpret_cast<void **>(&out_buf), &result);

    varint_encode<T>(buffer, length, out_buf, &out_size);

    return result;
}

template <typename T, auto F> static napi_value varint_encode_array_impl(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];
    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    uint32_t length = 0;
    napi_get_array_length(env, argv[0], &length);

    napi_value result;
    uint8_t *out_buf;
    std::vector<T> data(length);
    for (uint32_t i = 0; i < length; i++) {
        napi_value val;
        napi_get_element(env, argv[0], i, &val);
        if constexpr (sizeof(T) > 4) {
            F(env, val, data.data() + i, 0);
        } else {
            F(env, val, data.data() + i);
        }
    }

    size_t bytes = varint_bytes_total(data.data(), data.size());
    napi_create_arraybuffer(env, bytes, reinterpret_cast<void **>(&out_buf), &result);

    varint_encode<T>(data.data(), data.size(), out_buf, &bytes);

    return result;
}

template <typename T, auto F> static napi_value varint_get_bytes_impl(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];

    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    bool is_array = false;
    napi_is_array(env, argv[0], &is_array);

    uint32_t total = 0;
    T *in_buf = nullptr;
    uint32_t length = 0;

    if (is_array) {
        napi_get_array_length(env, argv[0], &length);
        std::vector<T> data(length);
        in_buf = data.data();
        for (uint32_t i = 0; i < length; i++) {
            napi_value val;
            napi_get_element(env, argv[0], i, &val);
            F(env, val, in_buf + i);
        }
        total = varint_bytes_total(in_buf, length);

    } else {
        size_t len;
        napi_get_typedarray_info(env, argv[0], nullptr, &len, reinterpret_cast<void **>(&in_buf), nullptr, nullptr);
        length = len;
        total = varint_bytes_total(in_buf, length);
    }

    napi_value result;
    napi_create_uint32(env, total, &result);
    return result;
}

template <typename T, auto F> static napi_value varint64_get_bytes_impl(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];

    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    bool is_array = false;
    napi_is_array(env, argv[0], &is_array);

    uint32_t total = 0;
    T *in_buf = nullptr;
    uint32_t length = 0;

    if (is_array) {
        napi_get_array_length(env, argv[0], &length);
        std::vector<T> data(length);
        in_buf = data.data();
        for (uint32_t i = 0; i < length; i++) {
            napi_value val;
            napi_get_element(env, argv[0], i, &val);
            F(env, val, in_buf + i, 0);
        }
        total = varint_bytes_total(in_buf, length);
    } else {
        size_t len;
        napi_get_typedarray_info(env, argv[0], nullptr, &len, reinterpret_cast<void **>(&in_buf), nullptr, nullptr);
        length = len;
        total = varint_bytes_total(in_buf, length);
    }

    napi_value result;
    napi_create_uint32(env, total, &result);
    return result;
}

static napi_value varint_get_count(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];
    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    bool is_arraybuffer;
    napi_is_arraybuffer(env, argv[0], &is_arraybuffer);

    uint8_t *data = nullptr;
    size_t length = 0;

    if (is_arraybuffer) {
        napi_get_arraybuffer_info(env, argv[0], reinterpret_cast<void **>(&data), &length);
    } else {
        napi_get_typedarray_info(env, argv[0], nullptr, &length, reinterpret_cast<void **>(&data), nullptr, 0);
    }

    size_t len = varint_count(data, length);
    napi_value result;
    napi_create_uint32(env, static_cast<uint32_t>(len), &result);
    return result;
}

template <typename T> static napi_value varint_decode_impl(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];
    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    bool is_arraybuffer = false;
    napi_is_arraybuffer(env, argv[0], &is_arraybuffer);

    uint8_t *data = nullptr;
    size_t length = 0;
    if (is_arraybuffer) {
        napi_get_arraybuffer_info(env, argv[0], reinterpret_cast<void **>(&data), &length);
    } else {
        napi_get_typedarray_info(env, argv[0], nullptr, &length, reinterpret_cast<void **>(&data), nullptr, 0);
    }

    napi_typedarray_type type;

    if constexpr (std::is_same_v<T, uint32_t>) {
        type = napi_uint32_array;
    } else if constexpr (std::is_same_v<T, int32_t>) {
        type = napi_int32_array;
    } else if constexpr (std::is_same_v<T, uint64_t>) {
        type = napi_biguint64_array;
    } else {
        type = napi_biguint64_array;
    }

    size_t count = varint_count(data, length);
    T *buffer = nullptr;

    napi_value arraybuffer, result;
    napi_create_arraybuffer(env, count * sizeof(T), reinterpret_cast<void **>(&buffer), &arraybuffer);
    napi_create_typedarray(env, type, count, arraybuffer, 0, &result);

    varint_decode<T>(data, length, buffer);

    return result;
}

constexpr inline auto varint_encode_typedarray = varint_encode_typedarray_impl<uint32_t>;
constexpr inline auto varint_zigzag_encode_typedarray = varint_encode_typedarray_impl<int32_t>;
constexpr inline auto varint64_encode_typedarray = varint_encode_typedarray_impl<uint64_t>;
constexpr inline auto varint64_zigzag_encode_typedarray = varint_encode_typedarray_impl<int64_t>;
constexpr inline auto varint_encode_array = varint_encode_array_impl<uint32_t, napi_get_value_uint32>;
constexpr inline auto varint_zigzag_encode_array = varint_encode_array_impl<int32_t, napi_get_value_int32>;
constexpr inline auto varint64_encode_array = varint_encode_array_impl<uint64_t, napi_get_value_bigint_uint64>;
constexpr inline auto varint64_zigzag_encode_array = varint_encode_array_impl<int64_t, napi_get_value_bigint_int64>;

constexpr inline auto varint_decode_s = varint_decode_impl<uint32_t>;
constexpr inline auto varint_zigzag_decode = varint_decode_impl<int32_t>;
constexpr inline auto varint64_decode = varint_decode_impl<uint64_t>;
constexpr inline auto varint64_zigzag_decode = varint_decode_impl<int64_t>;

constexpr inline auto varint_get_bytes = varint_get_bytes_impl<uint32_t, napi_get_value_uint32>;
constexpr inline auto varint_zigzag_get_bytes = varint_get_bytes_impl<int32_t, napi_get_value_int32>;

constexpr inline auto varint64_get_bytes = varint64_get_bytes_impl<uint64_t, napi_get_value_bigint_uint64>;
constexpr inline auto varint64_zigzag_get_bytes = varint64_get_bytes_impl<int64_t, napi_get_value_bigint_int64>;


#endif // MYAPPLICATION_VARIANT_H
